#include "VideoPboRender.h"
#include "base/YUVImage.h"
#include "base/YUVReader.h"
#include "render/VideoPboRender.h"
#include "utils/SnTimer.h"
#include "utils/util.h"
#include <cmath>
#include <iostream>
#include <memory>
#include <mutex>
#include <stdint.h>
#include <sys/stat.h>
#include <thread>
#include <utility>
#include <vcruntime.h>
#include <vcruntime_string.h>

static constexpr int kMaxFrame = 3;
static constexpr int kYUVWidth = 640;
static constexpr int kYUVHeight = 480;
static constexpr int kFrameGapMs = 1000 / 5;//帧率在5帧每秒
// static const std::string kYUVPATH = "./../res/image_640_480.yuv";
static const std::string kYUVPATH = "./../res/video_640_480.yuv";


namespace Sivin {
  VideoPboRender::VideoPboRender() {
    mVertexData = {
        //|---positon----|--texCoord--|
        {0.5f, 0.5f, 0.0f, 1.0f, 1.0f},//右上
        {-0.5, 0.5, 0.0f, 0.0f, 1.0f}, //左上
        {-0.5, -0.5, 0.0f, 0.0f, 0.0f},//左下
        {0.5, -0.5, 0.0f, 1.0f, 0.0f}};//右下

    mElementIndexArray = {
        0, 1, 2, // 第一个三角形
        0, 2, 3};// 第二个三角形

    mShader = std::make_unique<Shader>("./../shader/yuv/vert.vs", "./../shader/yuv/frag.fs");

    mReadThread = std::make_unique<std::thread>(&VideoPboRender::doReadYUVLoop, this);

    mYTexture.channel = YUVImage::Channel::Y;
    mYTexture.width = kYUVWidth;
    mYTexture.height = kYUVHeight;

    mUTexture.channel = YUVImage::Channel::U;
    mUTexture.width = kYUVWidth / 2;
    mUTexture.height = kYUVHeight / 2;

    mVTexture.channel = YUVImage::Channel::V;
    mVTexture.width = kYUVWidth / 2;
    mVTexture.height = kYUVHeight / 2;
  }

  VideoPboRender::~VideoPboRender() {
    mRunning = false;
    mCond.notify_one();
    if (mReadThread->joinable()) {
      mReadThread->join();
    }
  }

  void VideoPboRender::onGLInit() {
    mShader->init();

    initVAO();

    initTexture(mYTexture);
    initTexture(mUTexture);
    initTexture(mVTexture);

    mShader->start();
    mShader->setInt("yTexture", 0);
    mShader->setInt("uTexture", 1);
    mShader->setInt("vTexture", 2);

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, mYTexture.id);

    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, mUTexture.id);

    glActiveTexture(GL_TEXTURE2);
    glBindTexture(GL_TEXTURE_2D, mUTexture.id);
  }

  int count = 0;
  void VideoPboRender::onRender() {
    mShader->start();
    updateGPUData();
    glBindVertexArray(mVAO);
    glDrawElements(GL_TRIANGLES, mElementIndexArray.size(), GL_UNSIGNED_INT, (void *) 0);
    mShader->end();
    std::cout << "render count " << count++ << std::endl;
  }

  void VideoPboRender::onGLDestroy() {
    mRunning = false;
    mCond.notify_one();
  }

  void VideoPboRender::initVAO() {
    glGenVertexArrays(1, &mVAO);
    GLuint vbo, ebo;
    glGenBuffers(1, &vbo);
    glGenBuffers(1, &ebo);
    glBindVertexArray(mVAO);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint) * mElementIndexArray.size(),
                 mElementIndexArray.data(), GL_STATIC_DRAW);

    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(SnVertex) * mVertexData.size(),
                 mVertexData.data(), GL_STATIC_DRAW);

    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(SnVertex), (void *) 0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(SnVertex), (void *) offsetof(SnVertex, texCoord));

    glBindVertexArray(0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
  }

  void VideoPboRender::initTexture(YUVTexture &yuvTexture) {
    glGenBuffers(PBO_NUM, yuvTexture.pbo);
    for (int i = 0; i < PBO_NUM; ++i) {
      glBindBuffer(GL_PIXEL_UNPACK_BUFFER, yuvTexture.pbo[i]);
      glBufferData(GL_PIXEL_UNPACK_BUFFER, yuvTexture.width * yuvTexture.height, nullptr, GL_DYNAMIC_DRAW);
    }
    yuvTexture.id = SnUtil::createTextureId();
    //glTexImage2D定义了一张纹理，内部会分配纹理空间，指定纹理的格式
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, yuvTexture.width, yuvTexture.height, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr);
  }


  void VideoPboRender::updateGPUData() {
    std::unique_ptr<YUVImage> imageData{nullptr};
    pollYUVImage(imageData);
    if (imageData != nullptr) {
      sendDataToGPU(mYTexture, imageData);
      sendDataToGPU(mUTexture, imageData);
      sendDataToGPU(mVTexture, imageData);
    }
  }

  void VideoPboRender::sendDataToGPU(YUVTexture &yuvTexture, std::unique_ptr<YUVImage> &imageData) {
    int idx = yuvTexture.availablePboIdx % PBO_NUM;
    GLuint pbo = yuvTexture.pbo[idx];
    int size = yuvTexture.width * yuvTexture.height;
    GLsync sync = yuvTexture.sync[idx];
    //设置映射状态位
    GLbitfield mapFlag = GL_MAP_WRITE_BIT;
    if (sync != nullptr) {
      //检测该buffer是否渲染完成，等待时间10us
      GLenum result = glClientWaitSync(sync, 0, 10000);
      if (result == GL_WAIT_FAILED && result == GL_TIMEOUT_EXPIRED) {
        std::cout << "cpu too fast.\n";
        //这里表示当前帧还没有被渲染，或者正在处于渲染过程中。
        //如果是超时，我们可以选择再次等待，当然也可以使用下面的标志位
        //表示重新为该pbo创建一个新的buffer，丢弃原有的buffer
        mapFlag |= GL_MAP_INVALIDATE_BUFFER_BIT;
      } else {
        mapFlag |= GL_MAP_UNSYNCHRONIZED_BIT;
      }
      //删除同步对象
      glDeleteSync(sync);
    }

    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);

    //下面的代码没有经过测试：用途是重新创建pbo buffer
    // if (mapFlag & GL_MAP_INVALIDATE_BUFFER_BIT) {
    //   glBufferData(GL_PIXEL_UNPACK_BUFFER, yuvTexture.width * yuvTexture.height, nullptr, GL_DYNAMIC_DRAW);
    // }

    void *dataPtr = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0,
                                     yuvTexture.width * yuvTexture.height, mapFlag);
    if (dataPtr) {
      memcpy(dataPtr, imageData->getData(yuvTexture.channel), size);
      glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
    }
    //给纹理传输数据
    glBindTexture(GL_TEXTURE_2D, yuvTexture.id);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, yuvTexture.width, yuvTexture.height, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr);

    //创建同步对象
    yuvTexture.sync[idx] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
    yuvTexture.availablePboIdx = idx++;
  }

  void VideoPboRender::doReadYUVLoop() {
    YUVReader mReader;
    int ret = mReader.open(kYUVPATH, kYUVWidth, kYUVHeight, YUVImage::Format::YUV420);
    if (ret < 0) {
      std::cout << "open yuv file failed\n";
      return;
    }

    std::unique_ptr<YUVImage> yuvImage{nullptr};
    while (mRunning) {
      ret = mReader.readFrame(yuvImage);
      if (ret < 0) {
        mRunning = false;
      } else {
        addYUVImage(yuvImage);
      }
    }
    mReader.colse();
    std::cout << "yuv read thread end.\n";
  }

  void VideoPboRender::pollYUVImage(std::unique_ptr<YUVImage> &imageData) {
    int64_t now = SnTimer::now_ms();
    if (now - lastRenderPoint < kFrameGapMs) {
      return;
    }
    lastRenderPoint = now;
    std::unique_lock<std::mutex> lock{mMutex};
    if (mYuvImageQueue.empty()) {
      // mCond.wait(lock, [=] { return !mYuvImageQueue.empty(); });
      return;
    }
    imageData = std::move(mYuvImageQueue.front());
    mYuvImageQueue.pop();
    mCond.notify_one();
  }

  void VideoPboRender::addYUVImage(std::unique_ptr<YUVImage> &imageData) {
    std::unique_lock<std::mutex> lock{mMutex};
    if (mYuvImageQueue.size() >= kMaxFrame) {
      mCond.wait(lock, [=] { return mYuvImageQueue.size() < kMaxFrame || !mRunning; });
    }
    mYuvImageQueue.push(std::move(imageData));
  }

}// namespace Sivin